package cn.tedu.lucky.draw.server.aspect;


import cn.tedu.lucky.draw.server.common.annotation.OperationLog;
import cn.tedu.lucky.draw.server.protocol.po.UserLog;
import cn.tedu.lucky.draw.server.service.IUserLogService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;

@Aspect
@Component
public class LogAspect {

    @Autowired
    private IUserLogService userLogService;

    /**
     * 通过 @Pointcut 注解定义切入点，@annotation是切入点表达式的一种写法
     * 这里是表示基于OperationLog注解描述的方法作为切入点方法
     */
    @Pointcut("@annotation(cn.tedu.lucky.draw.server.common.annotation.OperationLog)")
    public void doLog(){
        //这个方法内部不写任何代码，此方法只用于承载当前注解的定义
    }

    @Around("doLog()")
    public Object doLogAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //方法调用前
        int status = 1; //初始状态
        String error=null;//初始错误信息
        long time=0;//耗时
        long t1=System.currentTimeMillis();//记录执行前的时间
        try{
            Object result=joinPoint.proceed();//调用目标方法业务
            long t2=System.currentTimeMillis();//记录执行后的时间
            time=t2-t1;
            return result;
        }catch (Throwable e){
            long t3 =System.currentTimeMillis();
            time=t3-t1;
            status=0;//表示出错了
            error=e.getMessage();
            throw e;
        }finally {
            saveOperationLog(joinPoint,time,status,error);
        }
    }

    private void saveOperationLog(ProceedingJoinPoint joinPoint, long time, int status, String error) throws NoSuchMethodException, JsonProcessingException {
        //HttpServletRequest
        //1.获取用户行为日志
        //1.1 获取IP地址
        //RequestContextHolder 是一个工具类，用于访问容器（Tmocat），对当前请求的包装
        ServletRequestAttributes requestAttributes
                = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        //getRequest方法用于获取当前处理线程的HttpServletRequest对象，使得我们可以在非直接处理请求的方法中访问request
        HttpServletRequest request = requestAttributes.getRequest();
        String ip = request.getRemoteAddr();
        //1.2 获取登陆用户
        String username="tedu";//这里先用一个默认用户名，后续获取登陆用户名
        //1.3 获取操作名
        //1.3.1 获取类的字节码文件
        Class<?> targetCls = joinPoint.getTarget().getClass();
        //1.3.2 获取方法签名对象
        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        Method method = targetCls.getDeclaredMethod(
                //提供方法名称
                ms.getName(),
                //参数列表类型
                ms.getParameterTypes()
        );
        //1.3.3 获取方法上的注解
        OperationLog annotation = method.getAnnotation(OperationLog.class);
        //1.3.4 获取操作名称
        String operation = annotation.operation();
        //1.4 获取方法名称（类全名+方法名） 例如：cn.tedu.draw.controller.ActivityController.saveActivity
        String methodPath = targetCls.getName() + "." + method.getName();

        Object[] args = joinPoint.getArgs();
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        //转换数据格式
        String params = objectMapper.writeValueAsString(args);

        //2.封装用户行为日志
        UserLog userLog = new UserLog();
        userLog.setIp(ip);
        userLog.setUsername(username);
        userLog.setCreateTime(LocalDateTime.now());
        userLog.setOperation(operation);
        userLog.setMethod(methodPath);
        userLog.setParams(params);
        userLog.setStatus(status);
        userLog.setTime(time);
        userLog.setError(error);
        userLogService.save(userLog);
    }
}
